package com.i2works.smartluolong.web.config;


import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.i2works.smartluolong.utils.config.ShiroConfigurationUtils;
import com.i2works.smartluolong.utils.shiro.SimpleWebSessionManager;
import com.i2works.smartluolong.utils.shiro.credentials.RetryLimitHashedCredentialsMatcher;
import com.i2works.smartluolong.utils.shiro.filter.IsMyServiceFilter;
import com.i2works.smartluolong.utils.shiro.filter.UrlRedirectFilter;
import com.i2works.smartluolong.utils.shiro.filter.UserLogoutFilter;
import com.i2works.smartluolong.utils.shiro.permission.UrlPermissionResolver;
import com.i2works.smartluolong.web.shiro.SmartWebRealm;
import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.session.mgt.AbstractSessionManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.Map;


/**
 * shiro配置类（采用ehcache缓存）
 *
 * @author Liu Yutao <koal@vip.qq.com>
 * @date 2017/12/18 14:05
 * <p>
 * <p>
 * 1.LifecycleBeanPostProcessor，这是个DestructionAwareBeanPostProcessor的子类，负责org.apache.shiro.util.Initializable类型bean的生命周期的，初始化和销毁。主要是AuthorizingRealm类的子类，以及EhCacheManager类。
 * 2.HashedCredentialsMatcher，这个类是为了对密码进行编码的，防止密码在数据库里明码保存，当然在登陆认证的生活，这个类也负责对form里输入的密码进行编码。
 * 3.ShiroRealm，这是个自定义的认证类，继承自AuthorizingRealm，负责用户的认证和权限的处理，可以参考JdbcRealm的实现。
 * 4.EhCacheManager，缓存管理，用户登陆成功后，把用户信息和权限信息缓存起来，然后每次用户请求时，放入用户的session中，如果不设置这个bean，每个请求都会查询一次数据库。
 * 5.SecurityManager，权限管理，这个类组合了登陆，登出，权限，session的处理，是个比较重要的类。
 * 6.ShiroFilterFactoryBean，是个factorybean，为了生成ShiroFilter。它主要保持了三项数据，securityManager，filters，filterChainDefinitionManager。
 * 7.DefaultAdvisorAutoProxyCreator，Spring的一个bean，由Advisor决定对哪些类的方法进行AOP代理。
 * 8.AuthorizationAttributeSourceAdvisor，shiro里实现的Advisor类，内部使用AopAllianceAnnotationsAuthorizingMethodInterceptor来拦截用以下注解的方法。
 */

@Configuration
public class ShiroEhcacheConfiguration extends ShiroConfigurationUtils {

    //后台会话名称
    @Value("${shiro.session.name}")
    private String shiroSessionName;

    //前台cookie名称
    @Value("${shiro.cookie.name}")
    private String shiroCookieName;

    //shiro会话路径
    @Value("${shiro.session.path}")
    private String shiroSessionPath;

    //shiro会话域名
    @Value("${shiro.session.domain}")
    private String shiroSessionDomain;

    //shiro会话记住我失效时间
    @Value("${shiro.remember.maxage}")
    private Integer shiroRememberMaxage;

    //shiro会话加密密钥
    @Value("${shiro.cipher.key}")
    private String shiroCipherKey;

    //shiro会话缓存名称（对应ehcache）
    @Value("${shiro.session.ehcache.name}")
    private String shiroSessionEhcacheName;

    //shiro方法缓存名称（对应ehcache）
    @Value("${shiro.method.ehcache.name}")
    private String shiroMethodEhcacheName;

    //未授权错误页面
    @Value("${shiro.error.url}")
    private String shiroErrorUrl;

    //用户登录页面
    @Value("${shiro.login.url}")
    private String shiroLoginUrl;

    //登录成功页面
    @Value("${shiro.success.url}")
    private String shiroSuccessUrl;

    //filterchain配置文件（可以不配置）
    @Value("${shiro.filterchain.url}")
    private String shiroFilterchainUrl;

    //日志
    private static final Logger logger = LoggerFactory.getLogger(ShiroEhcacheConfiguration.class);


    /**
     * (1).获取配置的Realm，之所以没使用注解配置，是因为此处需要考虑到加密处理
     */
    @Bean
    public SmartWebRealm smartWebRealm(EhCacheManager cacheManager) {
        logger.info("ShiroEhcacheConfiguration ----> SmartWebRealm");
        SmartWebRealm realm = new SmartWebRealm();
        //设置凭证匹配器
        realm.setCredentialsMatcher(getCredentialsMatcher(cacheManager));
        //设置缓存
        realm.setCacheManager(cacheManager);
        return realm;
    }


    /**
     * 凭证匹配器（由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     */
    @Bean
    public RetryLimitHashedCredentialsMatcher getCredentialsMatcher(EhCacheManager cacheManager) {
        logger.info("ShiroEhcacheConfiguration ----> 凭证匹配器：getCredentialsMatcher");
        RetryLimitHashedCredentialsMatcher matcher = new RetryLimitHashedCredentialsMatcher(cacheManager);
        //散列算法:这里使用MD5算法;
        matcher.setHashAlgorithmName("md5");
        //散列的次数，比如散列两次，相当于 md5(md5(""));
        matcher.setHashIterations(2);
        return matcher;
    }

    /**
     * 生命周期
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        logger.info("ShiroEhcacheConfiguration ----> 生命周期：getLifecycleBeanPostProcessor");
        return new LifecycleBeanPostProcessor();
    }


    /**
     * 会话ID生成器
     */
    @Bean
    public JavaUuidSessionIdGenerator getSessionIdGenerator() {
        logger.info("ShiroEhcacheConfiguration ----> 会话ID生成器：getSessionIdGenerator");
        return new JavaUuidSessionIdGenerator();
    }

    /**
     * 认证器
     * 设置ModularRealmAuthorizer的permissionResolver，其会自动设置到相应的Realm上
     */
    @Bean
    public ModularRealmAuthorizer getAuthorizer(SmartWebRealm smartWebRealm) {
        logger.info("ShiroEhcacheConfiguration ----> 认证器：getAuthorizer");
        ModularRealmAuthorizer authorizer = new ModularRealmAuthorizer();
        //Permission字符串匹配器
        authorizer.setPermissionResolver(new UrlPermissionResolver());
        authorizer.setRealms(Lists.newArrayList(smartWebRealm));
        return authorizer;
    }


    /**
     * (4).会话DAO.使用ehcache进行Session管理
     */
    @Bean
    public SessionDAO getSessionDAO(EhCacheManager cacheManager) {
        logger.info("ShiroEhcacheConfiguration ----> 会话DAO：getSessionDAO");
        EnterpriseCacheSessionDAO sessionDAO = new EnterpriseCacheSessionDAO();
        sessionDAO.setActiveSessionsCacheName(shiroSessionEhcacheName);
        sessionDAO.setSessionIdGenerator(getSessionIdGenerator());
        //缓存管理器
        sessionDAO.setCacheManager(cacheManager);
        return sessionDAO;
    }

    /**
     * (5).rememberMe管理器
     * 当应用开启了rememberMe时,用户下次访问时可以是一个user,但不会是authc,因为authc是需要重新认证的
     */
    @Bean
    public CookieRememberMeManager getRememberMeManager() {
        logger.info("ShiroEhcacheConfiguration ----> rememberMe管理器：getRememberMeManager");
        CookieRememberMeManager manager = new CookieRememberMeManager();
        manager.setCipherKey(Base64.decode(shiroCipherKey));
        // 会话模板
        Cookie cookie = new SimpleCookie(CookieRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME);
        // 缓存时间（单位是秒）
        cookie.setMaxAge(shiroRememberMaxage);
        // cookie作用域
        cookie.setDomain(shiroSessionDomain);
        // 配置存储Session
        manager.setCookie(cookie);
        return manager;
    }


//    /**
//     * 会话验证调度器，时间单位是毫秒
//     */
//    @Bean
//    @Qualifier("sessionManager")
//    public QuartzSessionValidationScheduler getQuartzSessionValidationScheduler(SimpleWebSessionManager sessionManager) {
//        logger.info("ShiroEhcacheConfiguration ----> 会话验证调度器：getQuartzSessionValidationScheduler");
//        QuartzSessionValidationScheduler sessionValidationScheduler = new QuartzSessionValidationScheduler();
//        //默认是60分钟，这里设置30分钟
//        sessionValidationScheduler.setSessionValidationInterval(DateUtils.MILLIS_PER_MINUTE * 30);
//        //设置调度的会话管理器
//        sessionValidationScheduler.setSessionManager(sessionManager);
//        sessionValidationScheduler.enableSessionValidation();
//        return sessionValidationScheduler;
//    }
//

    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        logger.info("ShiroEhcacheConfiguration ----> 开启shiro aop注解支持：getAuthorizationAttributeSourceAdvisor");
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    /**
     * (6).会话管理器(时间单位是毫秒)
     */
    @Bean(name = "sessionManager")
    public SimpleWebSessionManager getSessionManager(EhCacheManager cacheManager, SessionDAO sessionDAO) {
        logger.info("ShiroEhcacheConfiguration ----> 会话管理器：getSessionManager");
        SimpleWebSessionManager sessionManager = new SimpleWebSessionManager();
        //会话缓存管理器（ehcache）
        sessionManager.setCacheManager(cacheManager);
        //默认是30分钟
        sessionManager.setGlobalSessionTimeout(AbstractSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT);
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionValidationSchedulerEnabled(true);
//        sessionManager.setSessionValidationScheduler(scheduler);
        sessionManager.setSessionDAO(sessionDAO);
        sessionManager.setSessionIdCookieEnabled(true);
        //会话模板（默认超时时间-1，关闭浏览器即超时）
        SimpleCookie cookie = new SimpleCookie(shiroSessionName);
        cookie.setName(shiroSessionName);
        // cookie作用域
        cookie.setDomain(shiroSessionDomain);
        sessionManager.setSessionIdCookie(cookie);
        return sessionManager;
    }


    /**
     * (7).安全管理器
     */
    @Bean
    public DefaultWebSecurityManager getSecurityManager(SmartWebRealm smartWebRealm, EhCacheManager cacheManager, SimpleWebSessionManager sessionManager, RememberMeManager rememberMeManager, ModularRealmAuthorizer realmAuthorizer) {
        logger.info("ShiroEhcacheConfiguration ----> 安全管理器：getSecurityManager");
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 用户realm
        securityManager.setRealm(smartWebRealm);
        // 用户授权/认证信息Cache(ehcache)
        securityManager.setCacheManager(cacheManager);
        // 会话管理器
        securityManager.setSessionManager(sessionManager);
        //记住我管理器
        securityManager.setRememberMeManager(rememberMeManager);
        //认证器
        securityManager.setAuthorizer(realmAuthorizer);
        return securityManager;
    }

    /**
     * EhCache缓存管理器
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public EhCacheManager getEhcacheManager() {
        logger.info("ShiroEhcacheConfiguration ----> EhCache缓存管理器：getEhcacheManager");
        EhCacheManager shiroEhcacheManager = new EhCacheManager();
        shiroEhcacheManager.setCacheManagerConfigFile("classpath:config/ehcache.xml");
        return shiroEhcacheManager;
    }

    /**
     * 把ehcache注入到spring，便于前台调用
     */
    @Bean
    public EhCacheCacheManager getCacheManager(EhCacheManager ehCacheManager) {
        logger.info("ShiroEhcacheConfiguration ----> Spring Cache管理器：getCacheManager");
        EhCacheCacheManager springCacheManager = new EhCacheCacheManager();
        springCacheManager.setCacheManager(ehCacheManager.getCacheManager());
        return springCacheManager;
    }


    /**
     * 创建ShiroFilter
     */
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        logger.info("ShiroEhcacheConfiguration ----> 创建ShiroFilter：getShiroFilterFactoryBean");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 设置登陆地址（默认WEB-INF/login.jsp）
        shiroFilterFactoryBean.setLoginUrl(shiroLoginUrl);
        //登录成功后要跳转的连接，不设置的时候，会默认跳转到前一步的url(不建议设置)
        shiroFilterFactoryBean.setSuccessUrl(shiroSuccessUrl);
        // 设置权限认证失败跳转地址
        shiroFilterFactoryBean.setUnauthorizedUrl(shiroErrorUrl);
        // 拦截器链
        Map<String, Filter> filters = Maps.newLinkedHashMap();
        //基于表单验证的过滤器
        filters.put("authc", getFormAuthenticationFilter());
        //用户常量拦截器
        //filters.put("userConstant", getUserConstantFilter());
        //用户认证状态拦截器
        //filters.put("authStatus", getUserAuthStatusFilter());
        //业务归属拦截器
        filters.put("myService", getIsMyServiceFilter());
        //用户退出拦截器
        filters.put("userLogout", getUserLogoutFilter());
        shiroFilterFactoryBean.setFilters(filters);
        //加载权限规则
        loadShiroFilterChainRule(shiroFilterFactoryBean);
        return shiroFilterFactoryBean;
    }


    /**
     * 加载shiroFilter权限控制规则
     * <p>
     * 常用的包含anon、authc、perms、roles、user、logout。
     * 角色/权限信息由MyShiroCasRealm对象提供doGetAuthorizationInfo实现获取来的
     * authc绑定表单验证，如果表单拦截器传递参数不对，就会导致第一次登录无反应，第二次才能登陆成功的情况
     */
    private void loadShiroFilterChainRule(ShiroFilterFactoryBean shiroFilterFactoryBean) {
        shiroFilterFactoryBean.setFilterChainDefinitionMap(getShiroFilterChain(shiroFilterchainUrl));
    }

    /**
     * 基于Form表单的身份验证过滤器
     */
    private FormAuthenticationFilter getFormAuthenticationFilter() {
        FormAuthenticationFilter filter = new FormAuthenticationFilter();
        filter.setUsernameParam("userName");
        filter.setPasswordParam("password");
        filter.setRememberMeParam("rememeberMe");
        filter.setLoginUrl(shiroLoginUrl);
        filter.setSuccessUrl(shiroSuccessUrl);
        return filter;
    }

    /**
     * 退出拦截器
     */
    private UserLogoutFilter getUserLogoutFilter() {
        return new UserLogoutFilter();
    }

    /**
     * 跳转拦截器
     */
    private UrlRedirectFilter getUrlRedirectFilter() {
        return new UrlRedirectFilter();
    }

    /**
     * 当前业务是否属于当前用户的拦截器
     */
    public IsMyServiceFilter getIsMyServiceFilter() {
        return new IsMyServiceFilter();
    }

//    /**
//     * 用户认证状态拦截器
//     */
//    private UserAuthStatusFilter getUserAuthStatusFilter() {
//        return new UserAuthStatusFilter();
//    }
}
